<?php

/*
* 计算文章相似度
*/

class simDegree{
	
	// 定义常量
	public $aipnlp;
	public $wpPdo;

	public function __construct(){
		require_once( "../aip/AipNlp.php" );
		require_once( "common_word.php" );
		$this->aipnlp = new AipNlp('9947118', 'YdIHRWObHws1q250Ba8G3FHE', 'owzgxMD3g9W7CfRITNCxeFSKcXOdpzWD');
		$this->wpPdo  = new wp_pdo();

	}

	/*计算入口：传入一篇文章，获取5篇相似文章*/
	public function getsimarticle( $article ){
		global $wpdb;
		
		if(empty($article)){
			return "标题为空";
		}else{
			$needArray = $this->optimBaiduFenci($this->aipnlp->lexer($article));//获取待处理问题的分词数组
			//$allArticle = $wpdb->get_results('SELECT title,summary,tags,url FROM wp_article', ARRAY_A);//从数据库中抓取所有采集到的内容
			$allArticle = $this->wpPdo->get_article_tstu();

			foreach ($allArticle as $articleKey => $articleValue) {
				$artsummaryArray[$articleKey] = array($articleValue['title'], $articleValue['summary'], $articleValue['url']);//包含正式内容的数组
				$arttagsArray[$articleKey] = explode(',', $articleValue['tags']);//循环获取每篇文章的标签
			}

			foreach ($arttagsArray as $tagskey => $tagsvalue) {
				if(is_array($tagsvalue) && is_array($needArray)){
					$mergeTagsArray = array_unique(array_merge($tagsvalue, $needArray));//合并数组获取综合标签库
				}

				$needVector = $this->wordVector($article, $mergeTagsArray);
				$baseVector = $this->wordVector($artsummaryArray[$tagskey][0], $mergeTagsArray);
				$simDegreeArray[$tagskey] = $this->fingerDegree($needVector, $baseVector);

			}

			array_multisort($simDegreeArray, SORT_ASC, SORT_NUMERIC, $artsummaryArray);
			for($i=0; $i<5; $i++){
				$topfivesimilar[$i] = $artsummaryArray[$i];
			}

			return  $topfivesimilar;
		}
	}

	/**
	* @name   getSimArticle 获取相似文章
	* @param  array qstVct
	* @return array simArtArr
	*/
	public function getSimArticle_simple( $qstVct = array() ){

		#获取文库中所有的向量
		$allBaseArt = $this->wpPdo->get_article_vector();

		/**
		* @param array sortId      用于排序的id数组
		* @param array sortSimDeg  用于存储相似度的数组
		*/
		$sortId     = array();
		$sortSimDeg = array();

		# 获取目标向量的键值数组——多余，可用原始数组计算即可
		$tgtVctKeyArr = $this->getVctKeyArr( $qstVct );

		foreach ($allBaseArt as $docArt) {
			$sortId[]   = $docArt['id'];

			#获取当前文章的分词数组
			$docVct = array_unserialize( $docArt['vector'] );
			$docArtKeyArr = $this->getVctKeyArr( array_unserialize( $docArt['vector'] ) );

			#合并基础向量
			$baseArr = $this->mergeArr( $tgtVctKeyArr , $docArtKeyArr );

			#计算目标的格式化向量和文档的格式化向量
			//$tgtFmtVct = $this->wordVector( $tgtVctKeyArr , $baseArr );
			//$docFmtVct = $this->wordVector( $docArtKeyArr , $baseArr );

			#加权计算目标的向量
			$tgtFmtVct = $this->wordVectorWithWeight( $baseArr , $qstVct );
			$docFmtVct = $this->wordVectorWithWeight( $baseArr , $docVct );

			#计算相似度
			$sortSimDeg[] = $this->fingerDegree( $tgtFmtVct , $docFmtVct );
			
		}

		array_multisort( $sortSimDeg , SORT_ASC , SORT_NUMERIC , $sortId );

		return $sortId;
	}

	/**
	* @name   mergeArr  合并数组
	* @param  array tgtArr 目标数组
	* @param  array baseArr 基础数组
	* @return array allArr 
	*/
	public function mergeArr( $tgtArr = array() , $baseArr = array() ){
		if( is_array( $tgtArr ) && is_array( $baseArr ) ){
			return array_unique( array_merge( $tgtArr , $baseArr ) );
		}else{
			return false;
		}
	}

	/**
	* @name   getVctKeyArr 获取原始向量键值组成的数组
	* @param  array vector
	* @return array vctKeyArr
	*/
	public function getVctKeyArr( $vector = array() ){
		if( !is_array( $vector ) ){
			return false;
		}else{
			$vctKeyArr = array();
			foreach ($vector as $key => $value) {
				$vctKeyArr[] = $key;
			}
		}

		return $vctKeyArr;
	}

	/**
	* @name   formatVector 向量的格式化，将加权的向量，转化成0-1向量
	* @param  array needleVct
	* @return array needleVct 返回更新后的数组
	*/
	public function formatVector( $needleVct = array() ){
		if( is_string( $needleVct ) ){
			$needleVct = array_unserialize( $needleVct );
		}	

		foreach ($needleVct as $keyword => $wordcount) {
			if( 0 != $wordcount ){
				$needleVct[$keyword] = 1;
			}
		}

		return $needleVct;
	}

	/**
	* @name   mergeVct 合并待处理问题向量和文库向量，返回整合后的标准基向量
	* @param  array qstFmtVct 问题分词向量
	* @param  array docFmtVct 文库分词向量
	* @return array baseFmtVct 返回基向量  
	*/
	public function mergeVct( $qstFmtVct = array() , $docFmtVct = array() ){
		$baseFmtVct = array_unique( array_merge( $qstFmtVct , $docFmtVct ) );
		return $baseFmtVct;
	}


	/**
	* 相似度计算过程：传入两个文章向量，返回相似度
	* @return int simdegree  相似度指数
	*/
	public function fingerDegree($needVct, $baseVct){
		//对基础向量标准化
		$stdbaseVct   = $this->standardVector($baseVct);
		$dot_multiply = $this->dotpowVector($needVct, $stdbaseVct); //计算两个向量点乘
		$num_multiply = $this->powNumVector($dot_multiply, $stdbaseVct);//计算向量与常量积
		$plus_vector  = $this->plusVector($needVct, $num_multiply, '-'); //计算两个的差
		$fenzi        = $this->valueVector($plus_vector);
		$fenmu        = $this->valueVector($needVct);
		if($fenmu == 0){
			return false;
		}else{
			return round($fenzi/$fenmu, 5);
		}
		
	}

	/*将向量标准化：传入一个向量，每一项除以向量长度，实现向量的标准化*/
	public function standardVector($vct = array()){
		$value = $this->valueVector($vct);
		$newVector = array();
		if(!$value){
			return false;
		}else{
			foreach($vct as $v){
				$newVector[] = $v/$value;
			}
		}
		return $newVector;
	}

	/**
	* 生成分词数组：将百度分词后的数组，转化成1维词汇数组
	*/
	public function optimBaiduFenci( $baidufenci = array() ){
		if(!count($baidufenci)){
			return '分词结果为空';
		}
		$newArray = array();
		if(is_array($baidufenci['items'])){
			foreach($baidufenci['items'] as $word){
				$newArray[] = $word['item'];
			}
		}else{
			return "SOMETHING WRONG IN FILE:".__FILE__." AT LINE:".__LINE__."<br>";
		}
		
		
		return $newArray;
	}

	/**
	* @name   wordVector 成字符串分词向量：传入原始字符串和一维的分词数组
	* @param  array or string target
	* @param  array vctArray 基础分词向量
	* @return array vector 返回文章对照的格式化向量
	*/
	public function wordVector( $target, $vctArray = array() ){
		
		$vector = array();

		if( is_string( $target ) ){
			if( is_array($vctArray) ){
				foreach($vctArray as $word){
					$vector[] = substr_count( $target , $word );
				}
			}else{
				return false;
			}
		}elseif( is_array( $target ) ){
			foreach ( $vctArray as $word ) {
				if( array_search( $word, $target ) ){
					$vector[] = 1;
				}else{
					$vector[] = 0;
				}
			}
		}else{
			return false;
		}

		return $vector;
	}


	/**
	* @name   wordVectorWithWeight 获取文章加权向量
	* @param  array baseWordArr 基础分词数组
	* @param  array origWordArr 原分词加权向量
	* @return array wdFmtVctWht 加权分词向量
	*/
	public function wordVectorWithWeight( $baseWordArr = array() , $origWordArr = array() ){
		#初始化结果数组
		$wdFmtVctWht = array();

		#三个参数都必须是数组形势
		if( is_array( $baseWordArr ) && is_array( $origWordArr ) ){
			#循环基础分词数组
			foreach ($baseWordArr as $wValue) {
				if( isset( $origWordArr[$wValue] ) ){
					$wdFmtVctWht[] = $origWordArr[$wValue];
				}else{
					$wdFmtVctWht[] = 0;
				}
			}

			return $wdFmtVctWht;
		}else{
			return false;
		}
	}

	/**
	* 计算向量点乘：传入两个数字向量，计算点乘，注意，两个向量必须是等长
	*/
	public function dotpowVector( $vector1, $vector2 ){
		if(!$this->issimlengthVector($vector1, $vector2)){
			return "向量长度不一样";
		}
		$length = $this->issimlengthVector( $vector1, $vector2 );
		$dotpow = 0;
		for($i=0; $i<$length; $i++){
			$dotpow += $vector1[$i]*$vector2[$i];
		}
		
		return $dotpow;
	}

	/**
	* 常量与向量乘积：传入常量和向量，返回新向量
	*/
	public function powNumVector($num, $vector){
		if(is_array($vector)){
			foreach($vector as $v){
				$newVector[] = $v*$num;
			}
			return $newVector;
		}else{
			return false;
		}
		
	}

	/**
	* 向量加法：传入两个向量（等长），计算两个向量的加和减
	*/
	public function plusVector($vct1, $vct2, $type){
		$lengthofVector = $this->issimlengthVector( $vct1, $vct2 );//获取向量长度
		if(!$lengthofVector){
			return "向量长度不一样";
		}
		$newVector = array();
		if($type == '+'){
			for($i=0; $i<$lengthofVector; $i++){
				$newVector[] = $vct1[$i] + $vct2[$i];
			}
		}elseif($type == '-'){
			for($i=0; $i<$lengthofVector; $i++){
				$newVector[] = $vct1[$i] - $vct2[$i];
			}
		}else{
			return false;
		}
		
		return $newVector;
		
	}

	/**
	* @name   issimlengthVector 判断两个向量长度是否相同
	* @param  array vct 待比较的向量
	* @return int count 若相同，返回数组的长度
	*/
	public function issimlengthVector( $vct1 = array(), $vct2 = array() ){
		if( count($vct1) == count($vct2) ){
			return count( $vct1 );//如果相同就返回长度
		}else{
			return false;
		}
	}

	/**
	* @name   valueVector 向量长度值计算：输入一个向量，计算器绝对长度
	* @param  array vct 计算向量绝对长度
	* @return float value 绝对长度
	*/
	public function valueVector( $vct = array() ){
		$value = 0;
		if(is_array($vct)){
			foreach($vct as $v){
				$value += pow($v, 2);
			}
			
			return sqrt($value);
		}else{
			return false;
		}
		
	}

	///////////////////////////////BAIDU AI////////////////////////////////////////////////
	/**
	* @name   getSimDegreeByBaidu   通过百度ai的接口，计算相似度
	* @param  string  title
	* @param  string  content
	* @return float   simDeg
	*/
	public function bd_getSimDegree( $title = '' , $content = '' ){
		
		if( !mb_strlen( $title ) && !mb_strlen( $content ) ){
			echo "文章为空<br>";
		}

		#先匹配出来该文章的数个主要关键词一维数组
		@$keyword = $this->getKwdArr( $title );
		echo "文章的关键词包括：";
		var_dump( $keyword );

		#根据关键词来精准抓取文库信息
		$simKwdDoc = array();
		if( empty( $keyword ) ){
			die( '获取文章关键词为空' );
		}

		#获取相似文章
		$allSimArt = $this->bd_getAllSimlarArticles( $keyword );

		#计算与100条数据的相似度二维数组：包含相似度与文库文章的标题，摘要和链接
		$degOfSim = array();
		foreach ($allSimArt as $art) {
			$degOfSim[] = $this->bd_getArtSimDegree( $title , $art );
		}
		var_dump( $allSimArt );

		#对数组进行排序
		array_multisort( $degOfSim , SORT_ASC , SORT_NUMERIC , $allSimArt );
		var_dump( $allSimArt );

		#返回

	}

	/**
	* @name   bd_getArtSimDegree   获取文章的相似度
	* @param  string   title
	* @param  string   content
	* @return float    simDeg
	* @todo   暂不计算问题的描述
	*/
	public function bd_getArtSimDegree( $title = '' , $docArt = array() ){
		$ttlSimDeg = $this->aipnlp->simnet( $title , $docArt['title'] );
		//$cttSimDeg = $this->aipnlp->simnet( $content , $docArt['summary'] );
		return @$ttlSimDeg['score'];
		//return @$ttlSimDeg['score'] * 0.8 + @$cttSimDeg['score'] * 0.2;
	}


	/**
	* @name   bd_getAllSimlarArticles   匹配所有包含文章关键词的文章做匹配
	* @param  array   kwds
	* @return array   simArt
	*/
	public function bd_getAllSimlarArticles( $kwds = array() ){
		
		#合并关键词数组统一循环
		if( empty( $kwds['main'] )  || false == $kwds['main'] ){
			$kwdArr = $kwds['othr'];
		}elseif( empty( $kwds['othr'] ) || false == $kwds['othr'] ){
			$kwdArr = $kwds['main'];
		}else{
			$kwdArr = array_unique( array_merge( $kwds['main'] , $kwds['othr'] ) );
		}

		#循环获取相似文章id
		$simArt = array();
		foreach ($kwdArr as $value) {
			@$tmp = $this->wpPdo->getArtIdByLike( $value ); 
			if( !empty( $tmp ) ){
				$simArtId = array_merge( $simArt , $tmp );
			}
		}

		#通过id获取文章
		foreach ( array_unique( array_column( $simArtId , 'id' ) ) as $id ) {
			$simArt = array_merge( $simArt , $this->wpPdo->getArtById( $id ) );
		}

		return $simArt;
	}


	/**
	* @name   getKwdArr   获取文章的关键词数组
	* @param  string  title
	* @param  string  content
	* @return array   kwdArr
	*         string  firstKwd  每篇文章唯一的一个最长的关键词，用于数据库精准快速检索相关文章
	*         array   mainKwds  每篇文章中包含的项目相关主要关键词，可用于生成页面关键词和描述
	*         array   otherKwds 其他相关关键词，报考时间，其他主语等，可用于精准匹配  
	* @todo   暂不计算问题描述的关键词
	*/
	public function getKwdArr( $title = '' ){
		#按照标题长度，获取小于该长度的预设关键词进行匹配
		$kwds = $this->wpPdo->getkwdByLen_short( mb_strlen( $title ) );

		#建立一个删减掉关键词的副本，留作后面做更精细处理
		$copy_title = $title;
		#循环匹配标题中出现的关键词
		foreach ($kwds as $value) {
			if( false === strpos( $title , $value ) ){
				#没找到
				continue;
			}else{
				#包含该关键词，组合成主关键词数组mianKwds
				@$mainKwds[] = $value;
				$copy_title = str_replace( $value , '' , $copy_title );
			}
		}

		/**
		* @param   array  mainKwds  为主关键词 
		* @param   string firstKwd  为主关键词的第一个
		* @todo 通过copy_title 计算其他关键词
		*/
		#首要关键词
		@$firstKwd = $mainKwds[0];

		#其他关键词数组：对剩余的部分做分析,对copy_title进行分词分析,获取其他辅助关键词
		$otherKwds = $this->bd_getValidWords( $copy_title );

		return array( 
			'frst' => $firstKwd,
			'main' => $mainKwds,
			'othr' => $otherKwds,
			 );
	}

	/**
	* @name   getKeyWordN   获取名词类关键词
	* @param  string   title
	* @return array    nArr
	*/
	public function getKeyWordN( $title = '' ){
		$lexRel = $this->aipnlp->lexer( $title );
		$nArr = array();
		$validWordType = array( 'n' , 'nw' , 'an' , 'vn' , 'nz' , 'PER' , 'LOC' , "ORG" , 'TIME' );

		foreach ( @$lexRel['items'] as $word ) {
			if( in_array( $word['pos'] , $validWordType ) || in_array( $word['ne'] , $validWordType ) ){
				$nArr[] = $word['item'];
			}
		}
		return $nArr;
	}


	/**
	* @name   bd_getValidWords   保存百度分词之后最主要的几个词汇
	* @param  string   lastTitle
	* @return array    validWords
	*/
	public function bd_getValidWords( $lastTitle = '' ){

		#需要清除的词性包括：标点符号，普通动词，代词，量词，助词，介词，连词，其他虚词
		$deltypeArr = array( 'w' , 'u' , 'p' , 'xc' , 'c' , 'r' , 'v' , 'q' );
		$validWords     = array();

		if( !mb_strlen( $lastTitle ) ){
			echo "待处理的字符串为空";
			return false;
		}else{
			@$wordArr = $this->aipnlp->lexer( $lastTitle );	
		}

		foreach ( (array)@$wordArr['items'] as $word ) {
			if( !in_array( @$word['pos'] , $deltypeArr) ){
				$validWords[] = @$word['item'];
			}
		}

		return $validWords;
	}


}